package com.example.express.service;

import com.example.express.model.*;
import com.example.express.repo.*;
import com.example.express.util.Status;
import com.example.express.util.Util;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.transaction.Transactional;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Service
public class DomainService {
    @Autowired
    UserInfoRepo userInfoRepo;

    @Autowired
    TransPackageRepo transPackageRepo;

    @Autowired
    ExpressSheetRepo expressSheetRepo;

    @Autowired
    TransPackageContentRepo transPackageContentRepo;

    @Autowired
    TransNodeRepo transNodeRepo;

    @Autowired
    CustomerInfoRepo customerInfoRepo;

    /**
     *
     * @param pkg 某个包裹
     * @return 该包裹中的所有快件
     */
    private List<ExpressSheet> getListInPackage(TransPackage pkg) {
        return pkg.getTransPackageContents()
                .stream()
                .map(TransPackageContent::getExpressSheet)
                .collect(Collectors.toList());
    }

    /**
     *
     * @param id 快件id
     * @return 某个特定的快件
     */
    public ExpressSheet getEs(String id) {
        return expressSheetRepo.findById(id).orElseThrow(() -> new RuntimeException("No such ExpressSheet"));
    }

    /**
     *
     * @param es 由客户端传入的快件，包含了要保存快件的必要数据，此方法中只需再设置其他必要字段，即可保存
     * @return 完整的快件
     */
    public ExpressSheet newExpressSheet(ExpressSheet es) {
        UserInfo user = userInfoRepo.findById(Integer.valueOf(es.getAccepter()))
                .orElseThrow(() -> new RuntimeException("No such User"));
        String pkgId = user.getReceivePackageId();

        if (pkgId == null || pkgId.equals("")) {
            TransPackage transPackage = new TransPackage();
            pkgId = Util.genOrderNumber();
            transPackage.setId(pkgId);
            transPackage.setUserInfo(user);
            transPackage.setStatus(Status.NEW);

            user.setReceivePackageId(pkgId);

            transPackageRepo.save(transPackage);
            userInfoRepo.save(user);
        }

        TransPackage transPackage = transPackageRepo.findById(pkgId)
                .orElseThrow();
        transPackage.setTarget(transNodeRepo.findByNodeName(es.getSender().getAddress()));

        es.setStatus(Status.NEW); //状态0代表 “新建”
        es.setSender(customerInfoRepo.findByAddress(es.getSender().getAddress()).orElseGet(
                () -> {
                    CustomerInfo customerInfo = new CustomerInfo();
                    customerInfo.setAddress(es.getSender().getAddress());
                    customerInfoRepo.save(customerInfo);
                    return customerInfo;
                }
        ));
        es.setReveiver(customerInfoRepo.findByAddress(es.getReveiver().getAddress()).orElseGet(
                () -> {
                    CustomerInfo customerInfo = new CustomerInfo();
                    customerInfo.setAddress(es.getReveiver().getAddress());
                    customerInfoRepo.save(customerInfo);
                    return customerInfo;
                }
        ));

        es.setAccepteTime(new Timestamp(System.currentTimeMillis()));

        expressSheetRepo.save(es); //保存到数据库
        transPackageRepo.save(transPackage);

        moveExpressIntoPackage(es, transPackageRepo.findById(pkgId).get()); //把新生成的快件放入某快递员的收件包中
        return es;
    }

    /**
     * 揽收某个节点的新建{Status.NES}快件
     * @param node 节点名称（业务逻辑要求其唯一）
     */
    public void receiveEs(String node) {
        List<TransPackage> pkgs = transPackageRepo.findByTarget_NodeNameAndStatus(node, Status.NEW);
        for (TransPackage pkg : pkgs) {
            for (ExpressSheet expressSheet : getListInPackage(pkg)) {
                expressSheet.setStatus(Status.COLLECT);
                expressSheet.setAccepteTime(new Timestamp(System.currentTimeMillis()));
                expressSheetRepo.save(expressSheet);
            }

            pkg.getUserInfo().setReceivePackageId(null);
            userInfoRepo.save(pkg.getUserInfo());
        }
    }

    /**
     * 此方法分拣某个节点的以下相应包裹：
     * 1. 新建状态的包裹
     * 2. 分拣状态的包裹
     * @param node 节点名称
     */
    public void dispatchEs(String node) {
        Map<String, Map<String, String>> router = Util.initRouter();

        List<TransPackage> pkgs = transPackageRepo.findByTarget_NodeNameAndStatus(node, Status.NEW);
        pkgs.addAll(transPackageRepo.findByTarget_NodeNameAndStatus(node, Status.UNPACKAGE));

        for (TransPackage pkg : pkgs) {
            pkg.setStatus(Status.COMPLETE);
        }
        transPackageRepo.saveAll(pkgs);

        List<ExpressSheet> esList = new ArrayList<>();
        for (TransPackage pkg : pkgs) {
            esList.addAll(getListInPackage(pkg));
        }


        Map<String, List<ExpressSheet>> groupByNextNode = esList.stream()
                .collect(Collectors.groupingBy(
                        es -> router.get(node).getOrDefault(es.getReveiver().getAddress(), es.getReveiver().getAddress())
                ));

        for (String n : groupByNextNode.keySet()) {
            TransPackage transPackage;
            List<TransPackage> pkg = transPackageRepo.findByTarget_NodeNameAndStatus(n, Status.PACKAGE);
            if (pkg.size() == 0 || !pkg.get(0).getSource().getNodeName().equals(node)) {
                transPackage = new TransPackage();
                transPackage.setId(Util.genOrderNumber());
                transPackage.setCreateTime(new Timestamp(System.currentTimeMillis()));
                transPackage.setSource(transNodeRepo.findByNodeName(node));
                transPackage.setTarget(transNodeRepo.findByNodeName(n));

            } else
                transPackage = pkg.get(0);

            transPackage.setStatus(Status.PACKAGE);
            transPackageRepo.save(transPackage);
            for (ExpressSheet expressSheet : groupByNextNode.get(n)) {
                moveBetweenPackages(expressSheet, transPackage);
            }
        }
    }

    /**
     * 此方法的典型用例是：某个快递员，在某个节点{node}，选择运输某个包裹{pkgId}
     * 他要做的是：
     * 1. 判断节点名称和包裹状态
     * 2. 设置包裹的运输快递员
     * @param pkgId
     * @param node
     * @param pkg
     */
    public void setOffPkg(String pkgId, String node, TransPackage pkg) {
        transPackageRepo.findById(pkgId)
                .filter(p -> p.getSource().getNodeName().equals(node))
                .filter(p -> p.getStatus() == Status.PACKAGE)
                .map(p -> {
                    p.setUserInfo(pkg.getUserInfo());
                    int status;
                    if (p.getTarget().getNodeName().equals(node)) {
                        status = Status.DILIVE;
                    } else status = Status.TRANSFER;

                    p.setStatus(status);
                    for (ExpressSheet expressSheet : getListInPackage(p)) {
                        expressSheet.setStatus(status);
                    }
                    expressSheetRepo.saveAll(getListInPackage(p));
                    transPackageRepo.save(p);

                    return p;
                })
                .orElseThrow(() -> new RuntimeException("Current Node does not have this package or Current Package Status DO NOT permit setOff"));

    }

    /**
     * 此方法的典型用例是：某个正在运输包裹快递员到达包裹的目的地，点击到站后
     * 1. 此方法首先判断包裹状态是否允许此操作
     * 2. 设置包裹状态为 拆包{Status.UNPACK}，包裹中的所有快件状态为 分拣{Status.SORT}
     * @param pkgId
     */
    public void arrivePkg(String pkgId) {
        TransPackage pkg = transPackageRepo.findById(pkgId)
                .filter(p -> p.getStatus() != Status.TRANSFER)
                .map(p -> {
                    p.setStatus(Status.UNPACKAGE);
                    p.getUserInfo().setTransPackageId(null);
                    return p;
                })
                .orElseThrow(() -> new RuntimeException("Current package Status DO NOT permit arrive"));

        for (ExpressSheet expressSheet : getListInPackage(pkg)) {
            expressSheet.setStatus(Status.SORT);
            expressSheetRepo.save(expressSheet);
        }
        transPackageRepo.save(pkg);

        dispatchEs(pkg.getSource().getNodeName());
    }

    /**
     *
     * @param id
     * @param uid
     * @return
     */
    public ExpressSheet deliveryEs(String id, int uid) {
        ExpressSheet es = expressSheetRepo.findById(id).get();

        es.setDeliver(String.valueOf(uid));
        es.setStatus(Status.DILIVE);

        return expressSheetRepo.save(es);
    }

    private void moveExpressIntoPackage(ExpressSheet es, TransPackage pkg) {

        TransPackageContent transPackageContent = new TransPackageContent();

        transPackageContent.setTransPackage(pkg);
        transPackageContent.setExpressSheet(es);
        transPackageContent.setStatus(Status.ACTIVE); // 0代表Active

        transPackageContentRepo.save(transPackageContent);
    }

    private void moveExpressFromPackage(ExpressSheet es) {
        TransPackageContent pkg_add = transPackageContentRepo.findByExpressSheetAndStatus(es, Status.ACTIVE);
        pkg_add.setStatus(Status.OUT); // 1代表Out Of
        transPackageContentRepo.save(pkg_add);
    }

    /**
     * 此方法把包裹从原包中移出，并移入新包裹
     * @param expressSheet
     * @param to
     */
    @Transactional
    void moveBetweenPackages(ExpressSheet expressSheet, TransPackage to) {
        moveExpressFromPackage(expressSheet);
        moveExpressIntoPackage(expressSheet, to);
    }

    /**
     * 获取所有快件
     * @return
     */
    public List<ExpressSheet> getEss() {
        return (List<ExpressSheet>) expressSheetRepo.findAll();
    }

    /**
     * 按照节点名称和状态获取所有包裹
     * @param node
     * @param status
     * @return
     */
    public List<TransPackage> getPkgsByNodeAndStatus(String node, int status) {
        return transPackageRepo.findBySource_NodeNameAndStatus(node, status);
    }

    public List<TransPackage> getHistory(String id) {
        return transPackageContentRepo.findByExpressSheet_Id(id)
                .stream()
                .map(TransPackageContent::getTransPackage)
                .collect(Collectors.toList());
    }
}
